/*
 * WebServer.cpp
 *
 *  Created on: 16/06/2014
 *      Author: Vitor
 */

#include "WebServer.h"

WebServer::WebServer(int port) :
		welcome_socket(TCPSocket(nullptr, 8080)) {
	welcome_socket.open();
}

WebServer::~WebServer() {
	welcome_socket.close();
}

std::string WebServer::url_decode(const std::string& source) {
	std::string ret;
	char ch;
	int ii;
	for (unsigned int i = 0; i < source.length(); i++) {
		if (source[i] == '%') {
			sscanf(source.substr(i + 1, 2).c_str(), "%x", &ii);
			ch = static_cast<char>(ii);
			ret += ch;
			i = i + 2;
		} else {
			ret += source[i];
		}
	}
	return (ret);
}

std::string WebServer::convert_ip_to_string(unsigned int ip) {
	unsigned int bit24 = 0xFF000000;
	unsigned int bit16 = 0x00FF0000;
	unsigned int bit8 = 0x0000FF00;
	unsigned int bit0 = 0x000000FF;
	char ipAddr[20];
	sprintf(ipAddr, "%u.%u.%u.%u", (ip & bit0), (ip & bit8) >> 8,
			(ip & bit16) >> 16, (ip & bit24) >> 24);
	return std::string(ipAddr);
}

void WebServer::broadcast_to_peers(const std::vector<char>& message,
		bool including_myself) {
	Globals::peers.lock();
	for (auto it = Globals::peers.get().begin();
			it != Globals::peers.get().end(); it++) {
		if (!including_myself
				&& it->first == Globals::peer.get().get_ip_address()) {
			continue;
		}
		auto ip = convert_ip_to_string(it->first);
		TCPSocket socket(ip.c_str(), 7777);

		socket.open();
		socket.send(message);
		socket.close();
	}
	Globals::peers.unlock();
}

void WebServer::send_file(std::string full_file_name,
		std::vector<char>& file_contents, TCPSocket socket) {
	ByteQueue sendQueue;

	sendQueue.push<std::string>(full_file_name);
	sendQueue.push(&file_contents[0], file_contents.size());
	sendQueue.push_front<unsigned int>(sendQueue.size());
	sendQueue.push_front<char>(MessageTypes::INCOMING_FILE);

	socket.open();
	socket.send(sendQueue.get_raw());
	socket.close();
}

void WebServer::send_table() {
	std::string ip;

	auto byte_queue = Globals::file_table.get()->serialize();
	auto length = byte_queue.size();
	char type = (char) MessageTypes::FILE_TABLE;

	byte_queue.push_front<unsigned int>(length);
	byte_queue.push_front<char>(type);

	auto message = byte_queue.get_raw();

	broadcast_to_peers(message);
}

void WebServer::handle_new_file(std::string cur_dir, std::string file_name,
		std::vector<char>& file_contents) {
	if (file_name.empty()) {
		return;
	}
	if (Globals::file_table.get()->get_element_from_path(cur_dir + file_name)) {
		return;
	}
	Globals::peers.lock();
	unsigned int menor, menor2;
	unsigned int qmenor = 999999, qmenor2 = 999999, qtd;

	for (auto it = Globals::peers.get().begin();
			it != Globals::peers.get().end(); it++) {
		qtd = Globals::file_table.get()->quantidade_arquivos(it->first);
		if (qmenor > qtd) {
			qmenor2 = qmenor;
			menor2 = menor;
			qmenor = qtd;
			menor = it->first;
		} else if (qmenor2 > qtd) {
			qmenor2 = qtd;
			menor2 = it->first;
		}
	}
	Globals::peers.unlock();

	printf("%x %d\n%x %d\n", menor, qmenor, menor2, qmenor2);
	cur_dir = cur_dir.substr(1);

	auto ip = convert_ip_to_string(menor);
	auto ip2 = convert_ip_to_string(menor2);
	TCPSocket socket1(ip.c_str(), 7777);
	TCPSocket socket2(ip2.c_str(), 7777);
	send_file(cur_dir + file_name, file_contents, socket1);
	send_file(cur_dir + file_name, file_contents, socket2);

	FileDescriptor *new_file = new FileDescriptor(cur_dir + file_name, menor,
			menor2, file_contents.size());
	Globals::file_table.get()->add_file(new_file);
	send_table();
}

bool WebServer::handle_login(User user) {
	bool exist = false;

	Globals::user_table.lock();
	for (auto user_tabela : Globals::user_table.get()) {
		if (user_tabela.get_name() == user.get_name()
				&& user_tabela.get_password() == user.get_password()) {
			exist = true;
			break;
		}
	}
	Globals::user_table.unlock();
	if (exist) {
		Globals::logged_user.lock();
		Globals::logged_user.get() = user;
		Globals::logged_user.unlock();
		PageBuilder::build_home_page(Globals::logged_user.get());
	}
	return exist;
}

bool WebServer::handle_cadastro(User user) {
	Globals::user_table.lock();

	for (auto exist_user : Globals::user_table.get()) {
		if (exist_user.get_name() == user.get_name()) {
			Globals::user_table.unlock();
			return false;
		}
	}
	Globals::user_table.unlock();

	auto byte_queue = user.serialize();
	auto length = byte_queue.size();
	char type = (char) MessageTypes::NEW_USER;

	byte_queue.push_front<unsigned int>(length);
	byte_queue.push_front<char>(type);
	auto message = byte_queue.get_raw();

	broadcast_to_peers(message);

	return true;
}

std::string WebServer::put_slash(std::string name) {
	auto found = name.find_first_of("/");
	if (found == std::string::npos) {
		name += "/";
	} else {
		name = name.substr(0, found + 1);
	}
	return name;
}

void WebServer::handle_new_folder(std::string cur_dir, std::string new_folder) {
	if (new_folder.empty()) {
		return;
	}
	new_folder = url_decode(new_folder);
	new_folder = put_slash(new_folder);

	if (Globals::file_table.get()->get_element_from_path(
			cur_dir + new_folder)) {
		return;
	}

	Globals::file_table.lock();
	Globals::file_table.get()->make_directory(cur_dir + new_folder);
	send_table();
	Globals::file_table.unlock();
}

void WebServer::handle_rename_folder(std::string cur_dir, std::string old_name,
		std::string new_name) {
	if (new_name.empty()) {
		return;
	}
	new_name = put_slash(new_name);

	ByteQueue queue;

	queue.push<std::string>(cur_dir + old_name);
	queue.push<std::string>(cur_dir + new_name);
	queue.push_front<unsigned int>(queue.size());
	queue.push_front<char>(MessageTypes::RENAME_DIRECTORY);

	Globals::file_table.lock();
	Globals::file_table.get()->rename_directory(cur_dir + old_name,
			cur_dir + new_name);
	Globals::file_table.unlock();

	broadcast_to_peers(queue.get_raw(), false);
}

void WebServer::handle_rename_file(std::string cur_dir, std::string old_name,
		std::string new_name) {
	if (new_name.empty()) {
		return;
	}

	ByteQueue queue;

	queue.push<std::string>(cur_dir + old_name);
	queue.push<std::string>(cur_dir + new_name);
	queue.push_front<unsigned int>(queue.size());
	queue.push_front<char>(MessageTypes::RENAME_FILE);

	Globals::file_table.lock();
	Globals::file_table.get()->rename_file(cur_dir + old_name,
			cur_dir + new_name);
	Globals::file_table.unlock();

	broadcast_to_peers(queue.get_raw(), false);

	Thread balancer([] () {
		auto& balancer = Balancer::get_instance();
		balancer.run(Balancer::FILE_DELETION);
	});
	balancer.start();

	balancer.join();
}

void WebServer::handle_delete_file(std::string cur_dir, std::string file_name) {

	ByteQueue queue;

	cur_dir = cur_dir.substr(1);
	queue.push<std::string>(cur_dir + file_name);
	queue.push_front<unsigned int>(queue.size());
	queue.push_front<char>(MessageTypes::DELETE_FILE);

	Globals::file_table.lock();
	Globals::file_table.get()->remove_file(cur_dir + file_name);
	Globals::file_table.unlock();

	broadcast_to_peers(queue.get_raw(), false);

	Thread balancer([] () {
		auto& balancer = Balancer::get_instance();
		balancer.run(Balancer::FILE_DELETION);
	});
	balancer.start();

	balancer.join();
}

void WebServer::handle_delete_folder(std::string cur_dir,
		std::string dir_name) {

	ByteQueue queue;

	queue.push<std::string>(cur_dir + dir_name);
	queue.push_front<unsigned int>(queue.size());
	queue.push_front<char>(MessageTypes::DELETE_DIRECTORY);

	Globals::file_table.lock();
	Globals::file_table.get()->remove_directory(cur_dir + dir_name);
	Globals::file_table.unlock();

	broadcast_to_peers(queue.get_raw(), false);

	Thread balancer([] () {
		auto& balancer = Balancer::get_instance();
		balancer.run(Balancer::FILE_DELETION);
	});
	balancer.start();

	balancer.join();
}

void WebServer::handle_connection(TCPSocket client_socket_descriptor) {
	std::string server_path = "./html";

	HttpRequest request(client_socket_descriptor);
	HttpResponse response(client_socket_descriptor, HttpResponse::OK);
	response.add_header("Connection", "close");
	if (!request.receive_request()) {
		client_socket_descriptor.close();
		return;
	}

	if (request.get_method() == HttpRequest::POST) {
		auto post_handle = PostHandler(request);
		//LOGIN
		if (post_handle.has_value("user")) { //veio da pagina de login
			printf("Recebi um login\n");
			auto usr = post_handle.get_value("user");
			auto psw = post_handle.get_value("password");

			std::string user_name(usr.begin(), usr.end());
			std::string password(psw.begin(), psw.end());

			User user(user_name, password);

			if (!handle_login(user)) {
				response.add_header("Location", "/index.html");
				response.set_status(HttpResponse::SEE_OTHER);
				response.send_response();
				client_socket_descriptor.close();
				return;
			}
		}
		//CADASTRO
		else if (post_handle.has_value("newUser")) { //veio da pagina de cadastro
			//recebe e testa a senha e sua confirmacao
			auto usr = post_handle.get_value("newUser");
			auto psw = post_handle.get_value("password");

			std::string user_name(usr.begin(), usr.end());
			std::string password(psw.begin(), psw.end());

			User user(user_name, password);

			if (!(post_handle.get_value("password")
					== post_handle.get_value("Cpassword")
					&& handle_cadastro(user))) {
				response.add_header("Location", "/cadastro.html");
				response.set_status(HttpResponse::SEE_OTHER);
				response.send_response();
				client_socket_descriptor.close();
				return;
			}
		}
		//CRIAR PASTA
		else if (post_handle.has_value("newFolder")) {
			auto c_new_folder = post_handle.get_value("newFolder");
			std::string new_folder(c_new_folder.begin(), c_new_folder.end());
			auto cur_dir = request.get_file();

			handle_new_folder(cur_dir, new_folder);
		}
		//SUBIR ARQUIVO
		else if (post_handle.has_value("file")) {
			auto file_name = post_handle.get_filename("file");
			auto path = request.get_file();

			handle_new_file(path, file_name, post_handle.get_value("file"));
		}
		//renomear/deletar pasta/arquivo
		else if (post_handle.has_value("acao")) {
			auto c_acao = post_handle.get_value("acao");
			std::string acao(c_acao.begin(), c_acao.end());
			auto c_form = post_handle.get_value("form");
			std::string old_name(c_form.begin(), c_form.end());
			old_name = url_decode(old_name);
			auto c_new_name = post_handle.get_value("newName");
			std::string new_name(c_new_name.begin(), c_new_name.end());
			new_name = url_decode(new_name);
			//Acoes de Renomear
			if (acao == "Renomear") {
				//Pasta
				if (old_name[old_name.length() - 1] == '/') {
					handle_rename_folder(request.get_file(), old_name,
							new_name);
				}
				//Arquivo
				else {
					handle_rename_file(request.get_file(), old_name, new_name);
				}
			} else {
				//Pasta
				if (old_name[old_name.length() - 1] == '/') {
					handle_delete_folder(request.get_file(), old_name);
				}
				//Arquivo
				else {
					handle_delete_file(request.get_file(), old_name);
				}
			}
		}
	}

	if (request.get_file() == "/") {
		request.set_file("/index.html");
	} else if (request.get_file()[request.get_file().length() - 1] == '/') {
		PageBuilder::build_dir_page(request.get_file());
		request.set_file("/directory.html");
	}

	std::string complete_path;
	if (request.get_file().find("/files/") != std::string::npos) {
		complete_path += ".";
	} else {
		complete_path += server_path;
	}
	complete_path += request.get_file();

	File requested_file(complete_path, "rb");
	if (requested_file.exists()) {
		response.set_data(requested_file.read_file_contents());
	} else {
		fprintf(stderr, "Nao consegui abrir %s.\n", request.get_file().c_str());
		response.set_status(HttpResponse::NOT_FOUND);
	}
	response.send_response();

	client_socket_descriptor.close();
}

void WebServer::run() {
	while (Globals::active.get()) {
		TCPSocket client_socket_descriptor;
		if (welcome_socket.accept(client_socket_descriptor)) {
			Thread client_handler([client_socket_descriptor]() {
				handle_connection(client_socket_descriptor);
			});
			client_handler.start();
		}
		SDL_Delay(20);
	}
}
